# Session 与 Token
30 秒速记
- 都是解决
HTTP无状态下「你是谁」的问题,区别在状态存哪 Session:状态存服务端,客户端只拿一个随机的session id(通常放Cookie);好撤销,但要共享存储Token(常见JWT):用户信息签名后交给客户端,服务端验签就行,不用查库;好扩展,但难主动作废JWT只是签名不是加密,payload谁都能base64解出来,别放敏感信息- 实践:短期
access token+ 长期refresh token;不管哪种都要HTTPS,并防好XSS/CSRF
Session 和 Token 都是用来记住登录状态的,区别在于状态存在哪:Session 存在服务端,Token 把信息签好名交给客户端自己带着。 Session 的做法是登录后服务端生成一个随机 id,把用户信息存在内存或 Redis 里,浏览器只拿这个 id,所以想踢人下线删掉记录就行,代价是多台服务器要共享存储。Token 比如 JWT,用户 id、过期时间直接写进去再签名,服务端只验签不查库,天然适合多服务、跨域和 App,但签发出去就很难提前作废。所以实际项目里常配短有效期的 access token 加 refresh token。
理解「Session 与 Token」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 security 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
HTTP 本身不记得你是谁,每个请求都是独立的,所以需要一种机制在请求之间维持登录状态。Session 和 Token 是两种主流思路。
Session 是「服务端记账」。用 express-session 写起来是这样:
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 2 * 3600 * 1000 },
// 多实例部署时加 store: new RedisStore({ client: redisClient })
}));
app.post('/login', (req, res) => {
req.session.userId = 42; // 数据存在服务端,浏览器只拿到一个 connect.sid
res.json({ ok: true });
});
app.post('/logout', (req, res) => req.session.destroy(() => res.json({ ok: true })));
Token 是「客户端带证件」。以 JWT 为例,它由 header.payload.signature 三段组成:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ uid: 42 }, process.env.JWT_SECRET, { expiresIn: '15m' });
// 客户端之后在请求头里带上 Authorization: Bearer <token>
const payload = jwt.verify(token, process.env.JWT_SECRET); // { uid: 42, iat, exp }
// 签名不对或已过期会直接抛错
怎么选:传统的服务端渲染网站、需要随时踢人下线的后台,Session 简单可靠;多个服务共用登录态、要给 App 和第三方用的接口,Token 更方便。两种方案不是非此即彼,很多系统是 Session 管网页登录、Token 管开放接口。
面试官追问
追问 1JWT 里能放用户手机号吗?
别放。JWT 的 payload 只是 base64url 编码,复制到 jwt.io 就能看到原文,签名只保证内容没被改,不保证别人看不到。只放用户 id、角色、过期时间这类必要字段。
追问 2用户改了密码,之前签发的 JWT 怎么让它失效?
JWT 本身做不到,得加点状态:用户表存一个 tokenVersion,签发时写进 token,校验时对比,改密码就把版本号加一;或者把要作废的 jti 放进 Redis 黑名单直到它过期。
追问 3Token 存 localStorage 还是 Cookie?
localStorage 怕 XSS,脚本一跑就能读走;HttpOnly 的 Cookie 脚本读不到,但会自动带上,要防 CSRF,配 SameSite=Lax 基本够用。我更倾向 HttpOnly 加 Secure 加 SameSite 的 Cookie。
追问 4服务从一台扩到三台,用 Session 的登录老是掉,怎么办?
Session 存在各自进程内存里,请求分到别的机器就查不到。把 Session 存到 Redis,比如 express-session 配 connect-redis,三台机器读同一份。
追问 5用了 JWT 是不是就不用查数据库了?
验身份可以不查,但权限别全信 token 里的角色。用户被封禁或者降权后,旧 token 里的角色还是老的,敏感操作要再查一次当前状态。
# Session
30 秒速记
Session= 服务端给每个用户建一份档案,浏览器只拿一个编号(session id),下次来凭编号查档案- 编号一般放
Cookie自动带上;Cookie是载体,Session是服务端的存储,两者配合不是二选一 session id要随机不可猜,登录成功后要换一个新的,防会话固定攻击- 默认存进程内存,重启就丢、多实例不共享 → 线上存
Redis - 「不能跨域」说的是
Cookie受域名限制:子域共享靠Domain,跨站要credentials+SameSite=None; Secure+ 后端允许凭证
Session 就是服务端给每个登录用户建的一份档案,浏览器手里只拿一个编号,每次请求带着编号来,服务端按编号把档案翻出来。 编号通常放在 Cookie 里,所以 Cookie 是载体、Session 是服务端那份数据,两者是配合关系。好处是用户信息不出服务器,想让谁下线直接删档案。坑主要在存储:默认存进程内存,服务一重启或者扩成多实例,档案就找不到了,所以线上都存 Redis。常说的「Session 不能跨域」,本质是装编号的 Cookie 有域名限制,配好 CORS 凭证和 SameSite 是能带过去的。
Session 是一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上的进程中。
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上,这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。
如果说 Cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 Session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。
Session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
Session 不能跨域
面试官追问
追问 1把完整用户信息放进 Cookie,服务端什么都不存,这算 Session 吗?
不算,这是纯 Cookie 方案。没签名的话用户改一下 Cookie 就能冒充别人。Session 的要点是数据在服务端,客户端只有一个查档案用的随机 id。
追问 2登录成功后为什么要重新生成 session id?
防会话固定攻击:攻击者先拿到一个 id 塞给你,你用它登录后,他手里的 id 也成了登录态。express-session 里登录成功调一次 req.session.regenerate()。
追问 3运营后台在 admin.a.com,接口在 api.a.com,请求总是拿不到 Session,怎么办?
前端请求要带 credentials: 'include',后端返回 Access-Control-Allow-Credentials: true,并且 Allow-Origin 写具体域名不能写 *。两个子域同站,Cookie 设 Domain=.a.com 就能共享。
追问 4管理员要求能立刻把某个用户踢下线,Session 怎么做?
在 Redis 里按用户 id 维护一个他的 session id 列表,踢人时把这些 Session 全删了,用户下次请求就查不到档案,自然掉线。
追问 5用户量很大,Session 存储会不会扛不住?
一个 Session 一般就几百字节,百万在线也就几百 MB,Redis 完全扛得住。关键是设置合理的过期时间,别把大对象塞进 Session,存个用户 id 就够了,详细信息用时再查。
